//
// RS Game Framework
// Copyright © 2010 Jedd Haberstro
// jhaberstro@gmail.com
// 
// $Id:
//

#include "rs/graphics/Image.hpp"
#include "rs/math/HalfFloat.hpp"

namespace rs
{
    namespace graphics
    {
        namespace
        {
            inline UInt32 Index(RsSize x, RsSize y, RsSize width, RsSize channels) {
                return (x * channels) + (y * width * channels);
            }
            
            void BuildMipMapOld(UInt8* dst, UInt8 const* src, RsSize width, RsSize height, RsSize channels) {
                RsSize const blockSize = 2;
                RsSize mipWidth = width >> 1, mipHeight = height >> 1;
                
                for (RsSize j = 0; j < mipHeight; ++j) {
                    for (RsSize i = 0; i < mipWidth; ++i) {
                        for (RsSize c = 0; c < channels; ++c) {
                            Int32 const index = (j * blockSize) * width * channels + (i * blockSize) * channels + c;
                            Int32 ublock = blockSize, vblock = blockSize;
                            if (blockSize * (i + 1) > width) {
                                ublock = width - i * blockSize;
                            }
                            
                            if (blockSize * (j + 1) > height) {
                                vblock = height - j * blockSize;
                            }
                            
                            Int32 blockArea = ublock * vblock;
                            Int32 sum = blockArea >> 1;
                            sum += src[index];                                  // index + 0 * width * channels + 0 * channels
                            sum += src[index + channels];                       // index + 0 * width * channels + 1 * channels
                            sum += src[index + width * channels];               // index + 1 * width * channels + 0 * channels
                            sum += src[index + width * channels + channels];
                            dst[j * mipWidth * channels + i * channels + c] = sum / blockArea;
                        }
                    }
                }
            }
            
            template< typename T >
            void BuildMipMap(T* dst, T const* src, RsSize srcwidth, RsSize srcheight, TextureFormat const& format) {
                RsSize channels = format.GetChannelCount();
                for (RsSize y = 0; y < srcheight; y += 2) {
                    for (RsSize x = 0; y < srcwidth; y += 2) {
                        for (RsSize c = 0; c < channels; ++c) {
                            T sum = (
                                src[Index(x,     y,     srcwidth, channels) + c] +
                                src[Index(x + 1, y,     srcwidth, channels) + c] +
                                src[Index(x,     y + 1, srcwidth, channels) + c] + 
                                src[Index(x + 1, y + 1, srcwidth, channels) + c]
                            );
                            
                            sum /= 4;
                            *dst++ = sum;
                        }
                    }                    
                }
            }
        }
        
        RS_DEFINE_REFLECTION_CLASS(rs::graphics::Image);
        
        void Image::GenerateMipMaps(RsSize mipMapCount) {
            RS_ASSERT(mipMapCount > 0, "'mipMapCount' is negative");
            RS_ASSERT(GetFormat().IsPlain() || GetFormat().IsPacked(), "Unable to generate mipmaps for image's texture format");
            
            // Grow pixel data if needed
            mipMapCount = std::min(GetMaxMipMapCount(GetWidth(), GetHeight()), mipMapCount);
            if (mipMapCount > mipMapCount_) {
                mipMapCount_ = mipMapCount;
                pixels_ = static_cast< UInt8* >(std::realloc(pixels_, GetSize()));
            }
            
            // Generate the actual mipmaps
            #if 0
            UInt8* src = GetPixels(0), dst = GetPixels(1);
            for (RsSize level = 1; level < GetMipMapCount(); ++level) {
                RsSize width = GetWidth(level - 1);
                RsSize height = GetHeight(level - 1);
                UInt8 srcSize = dst - src, dstSize = GetWidth(level) * GetWidth(level) * format_.GetBytesPerPixel();
                
            }
            #endif
            
            UInt8 *src = const_cast< UInt8* >(GetPixels(0)), *dst = const_cast< UInt8* >(GetPixels(1));
            for (RsSize level = 1; level < GetMipMapCount(); ++level) {
                RsSize width = GetWidth(level - 1);
                RsSize height = GetHeight(level - 1);
                UInt8 srcSize = dst - src, dstSize = GetWidth(level) * GetWidth(level) * format_.GetBytesPerPixel();
                UInt32 bytesPerChannel = format_.GetBytesPerChannel();

                if (format_.IsUnsignedNormalized() || format_.IsUnsignedInteger()) {
                    if (bytesPerChannel == 8) {
                        BuildMipMap(reinterpret_cast< UInt8* >(src), reinterpret_cast< UInt8 const* >(dst), width, height, format_);
                    }
                    else if (bytesPerChannel == 16) {
                        BuildMipMap(reinterpret_cast< UInt16* >(src), reinterpret_cast< UInt16 const* >(dst), width, height, format_);
                    }
                    else if (bytesPerChannel == 32) {
                        BuildMipMap(reinterpret_cast< UInt32* >(src), reinterpret_cast< UInt32 const* >(dst), width, height, format_);
                    }
                }
                else if (format_.IsSignedNormalized() || format_.IsSignedInteger()) {
                    if (bytesPerChannel == 8) {
                        BuildMipMap(reinterpret_cast< Int8* >(src), reinterpret_cast< Int8 const* >(dst), width, height, format_);
                    }
                    else if (bytesPerChannel == 16) {
                        BuildMipMap(reinterpret_cast< Int16* >(src), reinterpret_cast< Int16 const* >(dst), width, height, format_);
                    }
                    else if (bytesPerChannel == 32) {
                        BuildMipMap(reinterpret_cast< Int32* >(src), reinterpret_cast< Int32 const* >(dst), width, height, format_);
                    }
                }
                else if (format_.IsFloat()) {
                    if (bytesPerChannel == 16) {
                        BuildMipMap(reinterpret_cast< rs::math::HalfFloat* >(src), reinterpret_cast< rs::math::HalfFloat const* >(dst), width, height, format_);
                    }
                    else if (bytesPerChannel == 32) {
                        BuildMipMap(reinterpret_cast< Float* >(src), reinterpret_cast< Float const* >(dst), width, height, format_);
                    }
                }
                else {
                    RS_ASSERT(false, "Unsupported mipmap generation for Image's texture format");
                }
                
                src += srcSize;
                dst += dstSize;
            }
        }
    }
}